package dk.itu.mobile.taskmanager;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.util.Util;

import dk.itu.mobile.util.*;
import dk.itu.mobile.vo.*;

public class TaskManager extends ReceiverAdapter{	

	private TaskList tasklist;
	private String path;
	private JChannel channel;	
	private String name;
	//All state updates are saved as String/xml in a list
	private final LinkedList<String> state = new LinkedList<String>();
	
	/**
	 * Constructor
	 * @param xmlPath path to task manager xml file
	 * @param taskManagerName name of task manager instance
	 * @throws Exception 
	 */
	public TaskManager(String xmlPath, String taskManagerName) throws Exception {
		path = xmlPath;	
		//Create tasklist
		tasklist = new TaskList();		
		tasklist = XMLSerializer.load(path, TaskList.class);
		//Set name
		name = taskManagerName;
		channel = new JChannel();
		startChannel();
		
	}
	

	private void startChannel() throws Exception {		
		//Setup channel
		//Add event listener
		channel.setReceiver(this);
		//Connect channel to TaskManager group
		channel.connect("TaskManagerCluster");
		
		//Get group state
		channel.getState(null, 0);
	}
	
	/**
	 * stop closes jgroup channel and saves xml file
	 * @throws InterruptedException 
	 */
	public void stop() throws InterruptedException {
		//Close channel
		this.channel.close();
		
		//Save xml into new file
		String xmlSavePath = path.substring(0, path.length() - 4) + "-" + name + ".xml";
		XMLSerializer.save(tasklist, xmlSavePath);	
	}
	
	/**
	 * sendStateUpdate send updates to the jgroup
	 * 
	 * @param command either "PUT", "POST" or "DELETE"
	 * @param object either User or Post object
	 */
	private void sendStateUpdate(String command, Object object) {
		//Get xml to send as update
		String xml = XMLSerializer.getXML(command, object);
		
		try {				
			//Wrap update xml in info about updater
			String update = "<updater manager='" + name + "'>" + xml +"</updater>";
				
			//Create message and send
			Message msg = new Message(null, null, update);				
			channel.send(msg);				
		} catch (Exception e) {
			
		}
	}
	
	/**
	 * handleStateUpdate handles updates
	 * @param xml
	 */
	private void handleStateUpdate(String xml) {			
		//Get info about updater
		int start = xml.indexOf("='");
		int mid = xml.indexOf("'>");
		String updater = xml.substring(start + 2, mid);
		
		//Get xml
		int end = xml.indexOf("</updater>");
		xml = xml.substring(mid + 2, end);
		
		//Ignore update by self
		if(!updater.equalsIgnoreCase(name)) {
			
			//Get object and command
			Object object = XMLSerializer.getObject(xml);		
			String command = XMLSerializer.getCommand(xml);
			
			//Add, update or delete object in tasklist
			if(object instanceof Task) {
				Task task = (Task) object;
				
				if(command.equalsIgnoreCase("put")) {
					tasklist.tasks.add(task);

				} else if (command.equalsIgnoreCase("post")){				
					tasklist.updateTask(task);	

				} else if (command.equalsIgnoreCase("delete")) {
					Task actualTask = tasklist.getTaskById(task.id);
					tasklist.removeTask(actualTask);
				}
				
			} else if (object instanceof User) {
				User user = (User) object;
				
				if(command.equalsIgnoreCase("put")) {
					tasklist.users.add(user);
					
				} else if (command.equalsIgnoreCase("post")){
					tasklist.updateUser(user);
					
				} else if (command.equalsIgnoreCase("delete")) {
					User actualUser = tasklist.getUserByName(user.name);
					tasklist.removeUser(actualUser);
				}			
			}						
		}
	}
	
	public void viewAccepted(View new_view) {
		System.out.println("TaskManager View: " + new_view + " accepted");
	}
	
	/**
	 * receive is called when a group update is received
	 */
	public void receive(Message msg) {
		String xml = msg.getObject().toString();	
		
		synchronized (state) {			
			state.add(xml);			
		}
		
		handleStateUpdate(xml);
		
	}
	
	public void getState(OutputStream output) throws Exception {
		synchronized (state) {
			Util.objectToStream(state, new DataOutputStream(output));
		}
	}
	
	public void setState(InputStream input) throws Exception {
		@SuppressWarnings("unchecked")
		List<String> list = (List<String>)Util.objectFromStream(new DataInputStream(input));		
		synchronized (state) {
			state.clear();
			state.addAll(list);
		}		
	
		//Handle all updates
		for(String xml: list) {
			handleStateUpdate(xml);
		}
	}
	
	//TaskManager methods
	/**
	 * Add a task to task manager instance and send updates to group
	 * @param name
	 * @param id
	 * @param date
	 * @param description
	 * @param users
	 */
	public void addTask(String name, String id, Date date, String description,
			List<User> users){
		Task task = new Task(name, id, date, description, users);		
		tasklist.tasks.add(task);
		
		sendStateUpdate("PUT", task);
	}
	
	/**
	 * Change a task on task manager instance and send updates to group
	 * @param name
	 * @param id
	 * @param date
	 * @param description
	 * @param users
	 */
	public void changeTask(String name, String id, Date date, String description,
			List<User> users){
		Task task = new Task(name, id, date, description, users);		
		tasklist.updateTask(task);

		sendStateUpdate("POST", task);
	}
	
	/**
	 * delete a task on task manager instance and send updates to group
	 * @param id
	 */
	public void removeTask(String id) {
		Task task = tasklist.getTaskById(id);		
		tasklist.removeTask(task);

		sendStateUpdate("DELETE", task);
	}
	
	/**
	 * Create a new user on task manager instance and send updates to group
	 * @param name
	 * @param password
	 */
	public void addUser(String name, String password) {
		User user = new User(name, password);		
		tasklist.users.add(user);

		sendStateUpdate("PUT", user);
	}
	
	/**
	 * Update a user on task manager instance and send updates to group
	 * @param name
	 * @param password
	 */
	public void updateUser(String name, String password) {
		User user = new User(name, password);		
		tasklist.updateUser(user);
		
		sendStateUpdate("POST", user);
		
		System.out.println(tasklist);
	}
	
	/**
	 * Remove a user on task manager instance and send updates to group
	 * @param name
	 */
	public void removeUser(String name) {
		User user = tasklist.getUserByName(name);		
		tasklist.removeUser(user);

		sendStateUpdate("DELETE", user);
	}
	
	
	/**
	 * Main method for testing class/objects
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		//Create 2 task manager instances		
		TaskManager manager1 = new TaskManager("data/group-task-manager1.xml", "Test1");
		TaskManager manager2 = new TaskManager("data/group-task-manager2.xml", "Test2");
		
		
		//Test user methods
		manager1.updateUser("Hector", "New PAss");
		manager1.removeUser("Hector");
		manager2.addUser("Funky Dude", "youw177n3w3rgu3ss");
		
		//Test task methods	
		Calendar testCalender = Calendar.getInstance();
		testCalender.set(2012, 12, 1);
		Date testDate = testCalender.getTime(); 
		List<User> testUserList = new ArrayList<User>();
		testUserList.add(new User("Funky Dude", "youw177n3w3rgu3ss"));		
		manager2.addTask("Cool Event", "coo1", testDate, "Ahhhh man. This will be awesome!", testUserList);
		manager1.removeTask("id-1");
		
		manager1.changeTask("Cooler Event", "coo1", testDate, "This will be awesome!", testUserList);
		
		manager1.stop();
		manager2.stop();
	}
	
}